home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
SGI Developer Toolbox 6.1
/
SGI Developer Toolbox 6.1 - Disc 4.iso
/
public
/
plan
/
src
/
daemon.c
< prev
next >
Wrap
C/C++ Source or Header
|
1994-08-01
|
17KB
|
662 lines
/*
* separate daemon program that reads the database and waits for events to
* trigger, and pops up menus or whatever the user specified. When a cycling
* event triggers, it is advanced in-core, but nothing is ever written back
* to the database file. The daemon re-reads the database whenever it
* receives a SIGHUP signal, which is sent by the main program after it
* writes the database. The daemon writes its pid to /tmp/.pland<UID>, or
* terminates if the file already exists. The purpose of this lockfile is
* to prevent multiple daemons, and to tell the plan program who to send
* the SIGHUP to.
*
* This program uses the $PATH environment variable to find programs.
*
* Author: thomas@bitrot.in-berlin.de (Thomas Driemeyer)
*/
#ifndef MIPS
#include <unistd.h>
#include <stdlib.h>
#endif
#include <stdio.h>
#ifndef VARARGS
#include <stdarg.h>
#endif
#include <sys/types.h>
#include <sys/wait.h>
#include <fcntl.h>
#include <signal.h>
#include <time.h>
#include <Xm/Xm.h>
#include "cal.h"
#ifndef RABBITS
#include <pwd.h>
#include <utmp.h>
#ifdef AIXV3
struct utmp *getutent();
#endif
#endif
#ifndef NOLIMITS
#include <limits.h>
#endif
#ifndef SIGCHLD /* for BSD systems */
#define SIGCHLD SIGCLD
#endif
#ifndef PIPE_MAX /* writing this many bytes never blocks */
#define PIPE_MAX 512
#endif
#if defined(BSD)||defined(MIPS) /* detach forked process from terminal */
#define ORPHANIZE setpgrp(0,0) /* session, and attach to pid 1 (init). */
#else /* This means we'll get no ^C and other */
#define ORPHANIZE setsid() /* signals. If -DBSD doesn't help, the */
#endif /* daemon works without, sort of. */
#if defined(SUN)||defined(BSD)||defined(SVR4) /* use itimer instead of sleep */
#define ITIMER /* systems, because not only may sleep be */
#endif /* uninterruptible, it may lose SIGHUP. */
#ifdef ITIMER
#include <sys/time.h>
#endif
#ifdef MIPS
extern struct passwd *getpwuid();
extern struct utmp *getutent();
#endif
/* #define DEBUG /* define this to get execution reports */
static popup_window(), send_mail(), write_script(), exec_program();
static char *get_subject(), *get_icontitle();
extern struct tm *time_to_tm();
extern void set_tzone();
extern time_t get_time();
extern BOOL find_file(), startup_lock();
#ifdef MIPS
extern char *getenv(), *malloc(), *realloc();
#endif
char *progname; /* argv[0] */
extern char lockpath[]; /* lockfile path (from lock.c) */
struct config config; /* global configuration data */
struct list *mainlist; /* list of all schedule entries */
extern time_t cutoff; /* all triggers before this are done */
static BOOL reread; /* caught SIGHUP, re-read mainlist */
extern int errno; /* system error */
static void sighand(), alert();
#ifndef VARARGS
void fatal(char *fmt, ...);
#endif
main(argc, argv)
char **argv;
int argc;
{
PID_T pid; /* pid in lockfile */
int reason; /* what triggers next? */
time_t now, twait; /* now and wait for trigger */
struct entry *entry; /* next entry that triggers */
int opt_k, opt_K; /* TRUE if -k or -K, resp. */
#ifdef ITIMER
struct itimerval itv; /* for sleep() replacement */
#endif
#ifndef RABBITS
struct utmp *u; /* for scanning /etc/utmp */
struct passwd *pw; /* user's password entry */
if (!(pw = getpwuid(getuid()))) {
fprintf(stderr, "%s: WARNING: can't read user name.\n%s\n", *argv,
"If you are running YP/NIS, you may have to recompile with -lsun");
}
#endif
progname = argv[0];
#ifndef DEBUG
(void)umask(0077);
setuid(getuid());
setgid(getgid());
if ((pid = fork()) > 0) {
sleep(1);
exit(0);
}
if (pid == -1)
fprintf(stderr, "%s: Warning: cannot fork\n", progname);
else
ORPHANIZE;
#endif
signal(SIGHUP, sighand);
signal(SIGINT, sighand);
signal(SIGQUIT, sighand); /* comment out any of */
signal(SIGILL, sighand); /* these if undefined */
signal(SIGBUS, sighand); /* */
signal(SIGSEGV, sighand); /* */
signal(SIGTERM, sighand); /* */
#ifdef ITIMER
signal(SIGALRM, sighand);
#endif
opt_k = argc > 1 && argv[1][0] == '-' && argv[1][1] == 'k';
opt_K = argc > 1 && argv[1][0] == '-' && argv[1][1] == 'K';
if (!startup_lock(LOCK_PATH, opt_k || opt_K)) {
if (opt_k || opt_K)
fatal("cannot kill existing %s daemon", progname);
else
fatal("another daemon exists, use %s -k", progname);
}
if (opt_K)
exit(0);
/*
* main loop
*/
tzset();
now = get_time();
set_tzone();
create_list(&mainlist);
(void)readfile(&mainlist, DB_PUB_PATH, TRUE);
(void)readfile(&mainlist, DB_PRIV_PATH, FALSE);
rebuild_repeat_chain(mainlist);
(void)recycle_all(mainlist, TRUE, 0);
#ifdef ITIMER
itv.it_interval.tv_sec = 0;
itv.it_interval.tv_usec = 0;
itv.it_value.tv_usec = 0;
#endif
for (;;) {
now = get_time();
set_tzone();
reason = find_next_trigger(mainlist, now, &entry, &twait);
if (reason && twait <= 0) {
alert(entry, reason);
entry->triggered = reason;
} else {
if (!reason)
twait = HIBERNATE;
# ifdef DEBUG
printf("%s: sleeping for %d:%02d:%2d\n", progname,
(int)twait/3600, (int)(twait/60)%60,
(int)twait%60);
# endif
cutoff = now;
#ifdef ITIMER
itv.it_value.tv_sec = twait;
setitimer(ITIMER_REAL, &itv, NULL);
#if defined(SVR4) || defined(SOLARIS2)
sigpause(SIGALRM);
#else
sigpause(0);
#endif
#else
sleep(twait);
#endif
}
if (reread) {
# ifdef DEBUG
printf("%s: re-reading database\n", progname);
# endif
destroy_list(&mainlist);
create_list(&mainlist);
(void)readfile(&mainlist, DB_PUB_PATH, TRUE);
(void)readfile(&mainlist, DB_PRIV_PATH, FALSE);
rebuild_repeat_chain(mainlist);
reread = FALSE;
}
(void)recycle_all(mainlist, TRUE, 0);
#ifndef RABBITS
if (pw) {
short pid = getpid();
setutent();
while (u = getutent())
if (u->ut_type == USER_PROCESS &&
u->ut_pid != pid &&
!strncmp(pw->pw_name, u->ut_user, 8))
break;
if (!u) {
# ifdef DEBUG
printf("%s: logout, exiting\n", progname);
# endif
exit(0);
}
}
#endif
}
}
/*--------------------------------- warn/alarm action -----------------------*/
/*
* An entry triggered, because its early-warn time, late-warn time, or alarm
* time was reached (reason is 1, 2, or 3, respectively). Do whatever was
* requested.
*/
static void alert(entry, reason)
register struct entry *entry; /* entry that triggered */
int reason; /* why: 1=early,2=late,3=time*/
{
switch(reason) {
case 1:
if (config.ewarn_window)
popup_window(reason, entry);
if (config.ewarn_mail)
send_mail(reason, entry);
if (config.ewarn_exec)
exec_program(entry, config.ewarn_prog, FALSE);
break;
case 2:
if (config.lwarn_window)
popup_window(reason, entry);
if (config.lwarn_mail)
send_mail(reason, entry);
if (config.lwarn_exec)
exec_program(entry, config.lwarn_prog, FALSE);
break;
case 3:
if (config.alarm_window)
popup_window(reason, entry);
if (config.alarm_mail)
send_mail(reason, entry);
if (config.alarm_exec)
exec_program(entry, config.alarm_prog, FALSE);
if (entry->script)
exec_program(entry, entry->script, TRUE);
}
}
/*
* pop up a window with the note text in it. This is done in a separate
* program that gets the text as standard input, to avoid having to drag
* in X stuff into the daemon. It is large enough as it is. The reason
* argument determines the color of the popup.
*/
static popup_window(reason, entry)
int reason; /* why: 1=early,2=late,3=time*/
register struct entry *entry; /* entry that triggered */
{
char prog[1024]; /* path of notifier program */
char cmd[2048]; /* command string */
if (!find_file(prog, ALARM_FN, TRUE)) {
fprintf(stderr, "%s: %s: not found\n", progname, ALARM_FN);
return;
}
if (config.wintimeout > 59)
sprintf(cmd, "%s -%d -e%d -t\1%s\2 -i\1%s\2",
prog, reason,
(int)config.wintimeout/60,
get_subject(reason, entry, FALSE),
get_icontitle(reason, entry));
else
sprintf(cmd, "%s -%d -t\1%s\2 -i\1%s\2",
prog, reason,
get_subject(reason, entry, FALSE),
get_icontitle(reason, entry));
exec_program(entry, cmd, FALSE);
}
/*
* send the entry's text to some user, as a mail message. The reason field
* is used for composing the Subject.
*/
static send_mail(reason, entry)
int reason; /* why: 1=early,2=late,3=time*/
register struct entry *entry; /* entry that triggered */
{
char cmd[1500]; /* command string */
char subject[82]; /* quoted subject */
if (!config.mailer || !*config.mailer) {
fprintf(stderr, "%s: no mailer defined\n", progname);
return;
}
strcpy(subject+1, get_subject(reason, entry, TRUE));
subject[0] = 1;
strcat(subject, "\2");
sprintf(cmd, config.mailer, subject);
exec_program(entry, cmd, FALSE);
}
/*
* execute a program, and pass the entry's message or note text as standard
* input. The program is executed as a child process, and reads the message
* from a pipe. The pipe is written to by a second child (unless the second
* fork fails, in which case the parent writes to the pipe) to avoid blocking
* the parent if the message is large and the child isn't really interested
* in the message. Blocking would freeze the daemon and could lose triggers.
* If the message is short (fits into one pipe write), don't use a 2nd child.
*/
/*ARGSUSED*/
static void reaper(sig) /* lay dead children to rest */
{
char path[40]; /* script to delete */
#if (defined BSD && !defined OSF)
union wait dummy;
#else
int dummy;
#endif
sprintf(path, "/tmp/pland%d", wait(&dummy));
# ifdef DEBUG
printf("%s: deleting script \"%s\"\n", progname, path);
# endif
(void)unlink(path);
signal(SIGCHLD, reaper);
}
static exec_program(entry, program, script)
register struct entry *entry; /* entry that triggered */
char *program; /* program or script to exec */
BOOL script; /* run program in a subshell */
{
int fd[2]; /* pipe to child process */
PID_T pid; /* child process id */
char *p = program; /* source command line */
char *q = program; /* target command line */
char *argv[100]; /* argument vector */
int argc = 0; /* argument counter */
char scriptname[40]; /* if script, temp file name */
int quote; /* argument quote flags */
char *msg; /* msg to print */
long msgsize; /* strlen(msg) */
char progpath[1024]; /* path name of executable */
if (!script) { /* build arguments */
if (!p) return;
while (argc < 99) {
argv[argc++] = q;
quote = 0;
while (*p && (quote | *p) != ' ') {
switch(*p) {
case '\'':
if (!(quote & 0x600))
quote ^= 0x100;
break;
case '"':
if (!(quote & 0x500))
quote ^= 0x200;
break;
case 1:
quote |= 0x400;
break;
case 2:
quote &= ~0x400;
break;
case '\\':
if (!quote && p[1])
p++;
default:
*q++ = *p;
}
p++;
}
if (!*p++)
break;
*q++ = 0;
while (*p == ' ')
p++;
}
*q = 0;
argv[argc] = 0;
if (!find_file(progpath, argv[0], TRUE)) {
FILE *err;
if (err = fopen("/dev/console", "w")) {
fprintf(err, "%s: %s: not found\n",
progname, argv[0]);
fclose(err);
}
return;
}
#ifdef DEBUG
{
int i;
printf("%d: EXECUTABLE = \"%s\"\n", progname,progpath);
for (i=0; i < argc; i++)
printf("%d: ARGV[%d] = \"%s\"\n",
progname, i, argv[i]);
}
#endif
}
if (pipe(fd)) { /* pipe from child 2 to ch 1 */
perror(progname);
return;
}
signal(SIGCHLD, reaper);
if ((pid = fork()) < 0) {
perror(progname);
return;
}
if (!pid) { /* child 1, execs program */
close(0);
dup(fd[0]);
close(fd[0]);
close(fd[1]);
ORPHANIZE;
if (script) {
write_script(scriptname, program);
strcpy(progpath, scriptname);
argv[0] = scriptname;
argv[1] = 0;
}
execv(progpath, argv);
perror(argv[0]);
exit(0);
}
pid = 1;
msg = entry->message ? entry->message :
entry->note ? entry->note : "";
msgsize = strlen(msg);
if (msgsize <= PIPE_MAX || /* child 2, feeds child 1 */
(pid = fork()) <= 0) {
close(fd[0]);
(void)write(fd[1], msg, msgsize);
close(fd[1]);
if (!pid)
exit(0);
} else { /* parent, do nothing */
close(fd[0]);
close(fd[1]);
}
}
/*
* write script to a file. The script name is generated from the process ID,
* so that reaper() knows which script to delete after this process terminates.
* The buffer for the script name is in the calling routine; it has to know
* because it is going to exec it. If something goes wrong, exit; we are in
* a child here. (Actually, this is what makes the whole thing look difficult:
* the script can't be written by the daemon, it must be written by the
* child that execs, because the script file name contains the child's PID,
* which isn't known before the fork. Putting the PID into the script file
* name makes it easy to delete the script file when the child dies, without
* keeping track of which child runs which script.)
* If the first line does not begin with "#!", "#!/bin/sh\n" is inserted.
*/
static write_script(scriptname, program)
char *scriptname; /* script name buffer */
char *program; /* script text */
{
int len; /* size of program */
int fd; /* script file */
sprintf(scriptname, "/tmp/pland%d", getpid());
# ifdef DEBUG
printf("%s: generating script \"%s\"\n", progname, scriptname);
# endif
if ((fd = open(scriptname, O_WRONLY | O_CREAT | O_TRUNC, 0777)) < 0) {
int err = errno;
fprintf(stderr, "%s: can't create %s: ", progname, scriptname);
errno = err;
perror("");
exit(1);
}
len = strlen(program);
if (program[0] != '#' && program[1] != '!' &&
write(fd, "#!/bin/sh\n", 10) != 10 ||
write(fd, program, len) != len) {
int err = errno;
fprintf(stderr, "%s: can't write to %s: ",progname,scriptname);
errno = err;
perror("");
close(fd);
(void)unlink(scriptname);
exit(1);
}
(void)write(fd, "\n", 1);
close(fd);
}
/*
* Return some descriptive message, to be used as a Subject or header text
* to tell the user what is happening. At most 79 characters are returned.
*/
static char *get_subject(reason, entry, withnote)
int reason; /* why: 1=early,2=late,3=time*/
register struct entry *entry; /* entry that triggered */
BOOL withnote; /* append note string too */
{
static char msg[80];
time_t time = (entry->time % 86400) / 60;
char ampm = 0;
if (config.ampm) {
ampm = time < 12*60 ? 'a' : 'p';
time %= 12*60;
if (time < 60)
time = 12*60;
}
sprintf(msg, reason==1 || reason==2 ? "Warning: alarm at %d:%02d%c"
: "ALARM at %d:%02d%c",
time/60, time%60, ampm);
if (withnote && entry->note != 0) {
int len = strlen(msg);
sprintf(msg+len, " (%.50s", entry->note);
len = strlen(msg);
if (msg[len-1] == '\n')
len--;
msg[len] = ')';
msg[len+1] = 0;
}
return(msg);
}
/*
* Return a short title string for the notifier icon title. It only
* contains the time of the trigger, and "early" or "late" flags.
*/
static char *get_icontitle(reason, entry)
int reason; /* why: 1=early,2=late,3=time*/
register struct entry *entry; /* entry that triggered */
{
static char msg[20];
time_t time = (entry->time % 86400) / 60;
char ampm = 0;
if (config.ampm) {
ampm = time < 12*60 ? 'a' : 'p';
time %= 12*60;
if (time < 60)
time = 12*60;
}
sprintf(msg, "%d:%02d%c", (int)(time/60), (int)(time%60), ampm);
if (reason == 1)
strcat(msg, " (E)");
if (reason == 2)
strcat(msg, " (L)");
return(msg);
}
/*--------------------------------- misc ------------------------------------*/
/*
* signal handler. SIGHUP re-reads the database by interrupting the sleep();
* all others terminate the program and delete the lockfile.
*/
static void sighand(sig)
int sig; /* signal type */
{
#ifdef ITIMER
if (sig == SIGALRM)
signal(SIGALRM, sighand);
else
#endif
if (sig == SIGHUP) { /* re-read database */
signal(SIGHUP, sighand);
reread = TRUE;
} else { /* die */
(void)unlink(lockpath);
fprintf(stderr, "%s: killed with signal %d\n", progname, sig);
exit(0);
}
}
/*
* whenever something goes seriously wrong, this routine is called. It makes
* code easier to read. fatal() never returns. This may fail horribly if
* VARARGS is defined, but at least it's going to do *something*.
*/
#ifndef VARARGS
/*VARARGS*/
void fatal(char *fmt, ...)
{
va_list parm;
int err = errno; /* fprintf may clear errno */
va_start(parm, fmt);
fprintf(stderr, "%s: ", progname);
vfprintf(stderr, fmt, parm);
va_end(parm);
putc('\n', stderr);
exit(1);
}
#else
/*VARARGS*/
fatal(fmt, a, b, c, d)
char *fmt;
int a, b, c, d;
{
fprintf(stderr, fmt, a, b, c, d);
putc('\n', stderr);
exit(1);
}
#endif
/*
* Ultrix doesn't have strdup, so we'll need to define one locally.
*/
char *mystrdup(s)
register char *s;
{
register char *p = NULL;
if (s && (p = (char *)malloc(strlen(s)+1)))
strcpy(p, s);
return(p);
}